package com.freeheart.aop.antireapte;


import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import static com.freeheart.aop.antireapte.AntiRepeatSubmit.RefuseStrategy;
import static com.freeheart.aop.antireapte.AntiRepeatSubmit.RefuseStrategy.RETURN_NULL;
import static com.freeheart.aop.antireapte.AntiRepeatSubmit.RefuseStrategy.THROW_EXCEPTION;

/**
 * 进行防重复提交的切面类
 *
 * @author: EmperorHeart
 * @date: 2025/4/5 0:46
 * @description
 */
@Aspect
public class AntiRepeatSubmitAspect {

    private final Logger logger = LoggerFactory.getLogger(AntiRepeatSubmitAspect.class);

    private final AntiRepeatLock lock;
    private final ExpressionParser parser;
    private final ParameterNameDiscoverer nameDiscoverer;
    private final BeanFactoryResolver resolver;


    public AntiRepeatSubmitAspect(AntiRepeatLock antiRepeatLock, ApplicationContext context) {
        this.lock = antiRepeatLock;
        this.parser = new SpelExpressionParser();
        this.nameDiscoverer = new DefaultParameterNameDiscoverer();
        this.resolver = new BeanFactoryResolver(context);
    }


    @Pointcut("@annotation(com.freeheart.aop.antireapte.AntiRepeatSubmit)")
    private void antiRepeatSubmitPointCut() {
    }

    @Around("antiRepeatSubmitPointCut()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        // 1.处理Key
        String key = parseKey(joinPoint);
        logger.debug("获取到的key:{}", key);
        // 2.拿到锁执行
        if (lock.acquireLock(key)) {
            try {
                return joinPoint.proceed();
            } catch (Throwable e) {
                throw new RuntimeException(e);
            } finally {
                logger.debug("key:{} 已释放", key);
                lock.unlock(key);
            }
        }
        // 3.没拿到锁，执行拒绝策略
        RefuseStrategy refuseStrategy = parseRefuseStrategy(joinPoint);
        if (THROW_EXCEPTION == refuseStrategy) {
            throw new RepeatSubmitException("不允许重复提交,key:" + key);
        }
        // 其他策略
        return null;

    }

    /**
     * 解析 RefuseStrategy
     *
     * @param joinPoint ProceedingJoinPoint
     * @return RefuseStrategy
     */
    private RefuseStrategy parseRefuseStrategy(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        if (signature instanceof MethodSignature methodSignature) {
            AntiRepeatSubmit submit = methodSignature.getMethod().getAnnotation(AntiRepeatSubmit.class);
            return submit.onRepeatSubmit();
        }
        return RETURN_NULL;
    }

    /**
     * 解析获取拿锁的Key
     *
     * @param joinPoint joinPoint
     * @return key
     */
    private String parseKey(ProceedingJoinPoint joinPoint) {
        // 一定是MethodSignature，因为是方法注解
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        String[] paramNames = nameDiscoverer.getParameterNames(signature.getMethod());
        // 1.构造 SpEL 上下文
        StandardEvaluationContext context = new StandardEvaluationContext();
        if (paramNames != null) {
            for (int i = 0; i < paramNames.length; i++) {
                // 把参数名和值放进去
                context.setVariable(paramNames[i], args[i]);
            }
        }
        // 1.1 支持使用'@beanName'方式获取Bean
        context.setBeanResolver(resolver);
        // 2.获取key
        AntiRepeatSubmit annotation = signature.getMethod().getAnnotation(AntiRepeatSubmit.class);
        String keyExpression = StringUtils.hasLength(annotation.key()) ? annotation.key() : annotation.value();
        // 解析 SpEL
        Expression expression = parser.parseExpression(keyExpression);
        return expression.getValue(context, String.class);
    }

}
